#! /usr/bin/env python
# coding=utf-8

import logging
import re
# import ipdb

from tornado.tcpserver import TCPServer
from tornado.iostream import StreamClosedError
from tornado.escape import json_decode, json_encode

from utils import parser_token
from utils.mapspack import mapsprotocol
from utils.constants import RET
from utils.logger import get_logger
from utils.session import session

logger = get_logger(__name__)


class FuturestarTCPServer(TCPServer):
    # clients = dict()
    response_data = {
        "success": {
            "code": "0",
            "data": {}
        },
        "reqerr": {
            "code": RET.REQERR,
            "data": {
                "msg": "Unsupported agreements"
            }
        },
        "tokenerr": {
            "code": RET.TOKENERR,
            "data": {
                "token": "TOKEN parsing exceptions"
            }
        },
        "cmderr": {
            "code": RET.NODATA,
            "data": {
                "cmd": "Command parsing exceptions"
            }
        }
    }

    def __init__(self, application, *args, **kwargs):
        super(FuturestarTCPServer, self).__init__(*args, **kwargs)
        self.headerlen = 4

        # todo
        self.application = application
        logger.info("app data => {}".format(application))

    async def handle_stream(self, stream, address):
        logger.info("new client is connation...{}".format(address))

        while True:
            try:
                await self.handle_message(stream, address[1])
            except StreamClosedError:
                self.on_close(stream, address[1])
                break

    async def handle_message(self, stream, address):
        # 1. 解析请求
        received_data = await self._read_message(stream, address)
        # logger.info("received_data <= {}".format(received_data))
        cmd = received_data.get('cmd')
        request_data = received_data.get('data')
        token = request_data.get('token')

        logger.info("cmd <= {}".format(cmd))
        logger.info("data <= {}".format(str(request_data))[:20]+"...")
        logger.info("token <= {}".format(token[:20]+ "..."))

        # 2. 用户认证处理
        status, uid, _ = self._user_auth(token)
        logger.info("token check status: {}".format(status))
        if not status:
            await self.send_message(stream, self.response_data.get('tokenerr'))
            self.on_close(stream, address)
            return

        # 3. 重新组装请求体包
        parse_data = dict(
            command=cmd,
            data=request_data,
            stream=stream,
            uid=uid,
        )

        # 4. 缓存用户信息
        # todo 缓存用户信息
        # 4.1  在线用户
        # 4.2  uid, stream

        # 5. 分发命令
        handler = await self.find_handler(cmd)
        if handler:
            # 5.1 命令处理
            logger.info("handering... <= {}".format(handler.__name__))
            logger.info("handler type => {}".format(type(handler)))
            logger.info("handler has init => {}".format(hasattr(handler, '__init__')))
            logger.info("handler has write => {}".format(hasattr(handler, 'write')))
            logger.info("parse_data => {}".format(str(parse_data)[:20]+"..."))
            # await handler(parse_data, self.application).write()
            # ipdb.set_trace()
            await handler(parse_data, self.application).write()
            # ipdb.set_trace()
        else:
            # 5.2 路由未找到
            logger.error("命令 {} 解析异常: {}".format(cmd, request_data))
            await self.send_message(stream, self.response_data.get('cmderr'))
            self.on_close(stream, address)

    async def _read_message(self, stream, address):
        """ 解析请求
        协议格式:
        {
            "cmd": nnnn,
            "data": {
                "token": "xxxxxxx"
            }
        }
        """

        # 读取 4B长度数据
        head = await stream.read_bytes(self.headerlen)
        # 解析 头部长度数据
        length = mapsprotocol.get_header(head)
        request_body = await stream.read_bytes(length)
        request_body_content = mapsprotocol.get_content_json(length, request_body)

        data = self.response_data.get('reqerr')
        if not isinstance(request_body_content, dict):
            await self.send_message(stream, data)
            self.on_close(stream, address)

        temp = request_body_content.keys()

        if not all(('cmd' in temp, 'data' in temp)):
            await self.send_message(stream, data)
            self.on_close(stream, address)
            return
        if not isinstance(request_body_content.get('cmd'), (str, int)):
            await self.send_message(stream, data)
            self.on_close(stream, address)
            return

        try:
            cmd = request_body_content['cmd']
            request_body_content['cmd'] = int(cmd)
        except Exception as e:
            logger.error("parse command error. {}".format(e))
            await self.send_message(stream, data)
            self.on_close(stream, address)
            return

        request_data = request_body_content.get('data')
        if not isinstance(request_data, dict):
            await self.send_message(stream, data)
            self.on_close(stream, address)
            return

        token = request_data.get('token')
        # logger.info("token <== {}".format(token[:20]))
        if not token:
            await self.send_message(stream, data)
            self.on_close(stream, address)
            return

        return request_body_content

    async def find_handler(self, cmd):
        """get handler object via request_route"""
        # assert isinstance(cmd, int), "cmd must be int."
        logger.info("cmd => {}, type => {}".format(cmd, type(cmd)))
        handler = None
        for rule in self.application.rules:
            route, target = rule
            pattern = re.compile("\d+")
            ret = pattern.fullmatch(route)

            if ret:
                route = int(route)
                logger.info("rule => {}".format(rule))
                logger.info("route => {}, cmd => {}".format(type(route), type(cmd)))
                if route == cmd:
                    handler = target
                    return handler
        return handler

    async def send_message(self, stream, data):
        """
        tcp发送数据
        """
        ret = mapsprotocol.inpack(json_encode(data))
        await stream.write(ret)

    def _user_auth(self, token: str):
        """
        :param token:
        :return: auth_status, uid, login_type
        """
        return parser_token(token)

    def on_close(self, stream, address):
        logging.info("A client has leave the chat room. {}".format(address))

        client_list = []
        for k, v in session.clients.items():
            if v == stream:
                client_list.append(k)

            for client in client_list:
                session.clients.pop(client)
        stream.close()
